<template>
  <div id="container"></div>
</template>

<script>
import * as THREE from "three";
import { EffectComposer } from "three/examples/jsm/postprocessing/EffectComposer.js";
import { RenderPass } from "three/examples/jsm/postprocessing/RenderPass.js";
import { ShaderPass } from "three/examples/jsm/postprocessing/ShaderPass.js";
import { BloomPass } from "three/examples/jsm/postprocessing/BloomPass.js";
import { FilmPass } from "three/examples/jsm/postprocessing/FilmPass.js";
import { FocusShader } from "three/examples/jsm/shaders/FocusShader.js";
import { OBJLoader } from "three/examples/jsm/loaders/OBJLoader.js";

export default {
  name: "PointsDynamic",
  data() {
    return {
      meshes: [],
      clonemeshes: [],
    };
  },
  mounted() {
    this.init();
    // this.animate();
  },
  methods: {
    init() {
      this.clock = new THREE.Clock();
      this.camera = new THREE.PerspectiveCamera(20, window.innerWidth / window.innerHeight, 1, 50000);
      this.camera.position.set(0, 700, 7000);
      this.scene = new THREE.Scene();
      this.scene.background = new THREE.Color(0x000104);
      this.scene.fog = new THREE.FogExp2(0x000104, 0.0000675);

      this.camera.lookAt(this.scene.position);

      const loader = new OBJLoader();
      let that = this;
      loader.load("https://threejs.org/examples/models/obj/male02/male02.obj", function (object) {
        const positions = that.combineBuffer(object, "position");

        that.createMesh(positions, that.scene, 4.05, -500, -350, 600, 0xff7744);
        that.createMesh(positions, that.scene, 4.05, 500, -350, 0, 0xff5522);
        that.createMesh(positions, that.scene, 4.05, -250, -350, 1500, 0xff9922);
        that.createMesh(positions, that.scene, 4.05, -250, -350, -1500, 0xff99ff);
      });

      loader.load("https://threejs.org/examples/models/obj/female02/female02.obj", function (object) {
        const positions = that.combineBuffer(object, "position");

        that.createMesh(positions, that.scene, 4.05, -1000, -350, 0, 0xffdd44);
        that.createMesh(positions, that.scene, 4.05, 0, -350, 0, 0xffffff);
        that.createMesh(positions, that.scene, 4.05, 1000, -350, 400, 0xff4422);
        that.createMesh(positions, that.scene, 4.05, 250, -350, 1500, 0xff9955);
        that.createMesh(positions, that.scene, 4.05, 250, -350, 2500, 0xff77dd);
      });

      this.renderer = new THREE.WebGLRenderer();
      this.renderer.setPixelRatio(window.devicePixelRatio);
      this.renderer.setSize(window.innerWidth, window.innerHeight);
      this.renderer.autoClear = false;
      document.getElementById("container").appendChild(this.renderer.domElement);

      this.parent = new THREE.Object3D();
      this.scene.add(this.parent);

      const grid = new THREE.Points(new THREE.PlaneGeometry(15000, 15000, 64, 64), new THREE.PointsMaterial({ color: 0xff0000, size: 10 }));
      grid.position.y = -400;
      grid.rotation.x = -Math.PI / 2;
      this.parent.add(grid);

      // postprocessing

      const renderModel = new RenderPass(this.scene, this.camera);
      const effectBloom = new BloomPass(0.75);
      const effectFilm = new FilmPass(0.5, 0.5, 1448, false);

      let effectFocus = new ShaderPass(FocusShader);

      effectFocus.uniforms["screenWidth"].value = window.innerWidth * window.devicePixelRatio;
      effectFocus.uniforms["screenHeight"].value = window.innerHeight * window.devicePixelRatio;

      this.composer = new EffectComposer(this.renderer);

      this.composer.addPass(renderModel);
      this.composer.addPass(effectBloom);
      this.composer.addPass(effectFilm);
      this.composer.addPass(effectFocus);

      window.addEventListener("resize", this.onWindowResize());
    },
    combineBuffer(model, bufferName) {
      let count = 0;
      model.traverse(function (child) {
        if (child.isMesh) {
          const buffer = child.geometry.attributes[bufferName];

          count += buffer.array.length;
        }
      });

      const combined = new Float32Array(count);
      let offset = 0;
      model.traverse(function (child) {
        if (child.isMesh) {
          const buffer = child.geometry.attributes[bufferName];

          combined.set(buffer.array, offset);
          offset += buffer.array.length;
        }
      });

      return new THREE.BufferAttribute(combined, 3);
    },
    createMesh(positions, scene, scale, x, y, z, color) {
      const geometry = new THREE.BufferGeometry();
      geometry.setAttribute("position", positions.clone());
      geometry.setAttribute("initialPosition", positions.clone());

      geometry.attributes.position.setUsage(THREE.DynamicDrawUsage);

      const clones = [
        [6000, 0, -4000],
        [5000, 0, 0],
        [1000, 0, 5000],
        [1000, 0, -5000],
        [4000, 0, 2000],
        [-4000, 0, 1000],
        [-5000, 0, -5000],

        [0, 0, 0],
      ];
      let mesh;
      for (let i = 0; i < clones.length; i++) {
        const c = i < clones.length - 1 ? 0x252525 : color;

        mesh = new THREE.Points(geometry, new THREE.PointsMaterial({ size: 30, color: c }));
        mesh.scale.x = mesh.scale.y = mesh.scale.z = scale;

        mesh.position.x = x + clones[i][0];
        mesh.position.y = y + clones[i][1];
        mesh.position.z = z + clones[i][2];

        this.parent.add(mesh);

        this.clonemeshes.push({ mesh: mesh, speed: 0.5 + Math.random() });
      }

      this.meshes.push({
        mesh: mesh,
        verticesDown: 0,
        verticesUp: 0,
        direction: 0,
        speed: 15,
        delay: Math.floor(200 + 200 * Math.random()),
        start: Math.floor(100 + 200 * Math.random()),
      });
    },
    animate() {
      requestAnimationFrame(this.animate);
      this.render();
    },
    render() {
      let delta = this.clock.getDelta();

      this.parent.rotation.y += -0.5 * delta;

      for (let j = 0; j < this.clonemeshes.length; j++) {
        const cm = this.clonemeshes[j];
        cm.mesh.rotation.y += -0.1 * delta * cm.speed;
      }

      for (let j = 0; j < this.meshes.length; j++) {
        const data = this.meshes[j];
        const positions = data.mesh.geometry.attributes.position;
        const initialPositions = data.mesh.geometry.attributes.initialPosition;

        const count = positions.count;

        if (data.start > 0) {
          data.start -= 1;
        } else {
          if (data.direction === 0) {
            data.direction = -1;
          }
        }

        for (let i = 0; i < count; i++) {
          const px = positions.getX(i);
          const py = positions.getY(i);
          const pz = positions.getZ(i);

          // falling down
          if (data.direction < 0) {
            if (py > 0) {
              positions.setXYZ(i, px + 1.5 * (0.5 - Math.random()) * data.speed * delta, py + 3.0 * (0.25 - Math.random()) * data.speed * delta, pz + 1.5 * (0.5 - Math.random()) * data.speed * delta);
            } else {
              data.verticesDown += 1;
            }
          }

          // rising up
          if (data.direction > 0) {
            const ix = initialPositions.getX(i);
            const iy = initialPositions.getY(i);
            const iz = initialPositions.getZ(i);

            const dx = Math.abs(px - ix);
            const dy = Math.abs(py - iy);
            const dz = Math.abs(pz - iz);

            const d = dx + dy + dx;

            if (d > 1) {
              positions.setXYZ(
                i,
                px - ((px - ix) / dx) * data.speed * delta * (0.85 - Math.random()),
                py - ((py - iy) / dy) * data.speed * delta * (1 + Math.random()),
                pz - ((pz - iz) / dz) * data.speed * delta * (0.85 - Math.random())
              );
            } else {
              data.verticesUp += 1;
            }
          }
        }

        // all vertices down
        if (data.verticesDown >= count) {
          if (data.delay <= 0) {
            data.direction = 1;
            data.speed = 5;
            data.verticesDown = 0;
            data.delay = 320;
          } else {
            data.delay -= 1;
          }
        }

        // all vertices up
        if (data.verticesUp >= count) {
          if (data.delay <= 0) {
            data.direction = -1;
            data.speed = 15;
            data.verticesUp = 0;
            data.delay = 120;
          } else {
            data.delay -= 1;
          }
        }

        positions.needsUpdate = true;
      }

      this.composer.render(0.001);
    },
    onWindowResize() {
      this.renderer.setSize(window.innerWidth, window.innerHeight);
      this.camera.aspect = window.innerWidth / window.innerHeight;
      this.camera.updateProjectionMatrix();
    },
  },
};
</script>

<style lang="scss"></style>
